Passed
Push — master ( 2a3177...723b76 )
by Night
01:11
created

stringFuncs.loadCSV   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 2
eloc 6
c 2
b 0
f 1
nc 2
nop 5
dl 0
loc 12
rs 10
1
/** global: UB */
2
//removeIf(nodejs)
3
4
/* File Utils - NodeJS only */
5
6
var fs = require('fs');
7
var pathUtil = require('path');
8
9
var arrayFuncs = {
10
11
	saveToCSV: function(filePath, headers = null, trimValues = true, columnar = false, fixedSep = ",") {
12
		var data = this;
13
14
		// write CSV to string
15
		var str = data.encodeCSV(headers, trimValues, columnar, fixedSep);
16
		
17
		// save data as string via filestream
18
		str.saveToText(filePath);
19
	},
20
21
	/** Fast and simple CSV generator.
22
		Supports multi-line values and values with double quotes.
23
		Automatic/smart escaping like Excel-generated CSVs. */
24
	encodeCSV: function(headers, trimValues, columnar, seperator = ",") {
25
		var linesData = this;
26
		
27
		if (columnar) {
28
			linesData = linesData.transpose();
29
		}
30
		
31
		var sb = [];
32
		
33
		// per line
34
		for (var l = (headers != null ? -1 : 0), ll = linesData.length - 1; l <= ll; l++) {
1 ignored issue
show
Best Practice introduced by
Comparing headers to null using the != operator is not safe. Consider using !== instead.
Loading history...
35
			
36
			// fetch header / line data words
37
			var words = l == -1 ? headers : linesData[l];
38
			
39
			// per word
40
			for (var v = 0, vl = words.length - 1; v <= vl; v++) {
41
				
42
				// if value given
43
				if (words[v] != null) {
1 ignored issue
show
Best Practice introduced by
Comparing words.v to null using the != operator is not safe. Consider using !== instead.
Loading history...
44
					
45
					// convert value to text
46
					var word = words[v].toString();
47
					if (trimValues) {
48
						word = word.trim();
49
					}
50
					
51
					// add word
52
					if (word.indexOf('"') > -1) {
53
						
54
						// escape quotes, and enclose in quotes if word has a quote
55
						sb.push('"');
56
						sb.push(word.split("\"").join("\"\""));
57
						sb.push('"');
58
						
59
					} else if (word.indexOf(',') > -1) {
60
						
61
						// enclose in quotes if word has comma
62
						sb.push('"');
63
						sb.push(word);
64
						sb.push('"');
65
						
66
					} else {
67
						
68
						// add straightaway otherwise
69
						sb.push(word);
70
					}
71
				}
72
				
73
				// add seperator
74
				if (v < vl) {
75
					sb.push(seperator);
76
				}
77
			}
78
			
79
			// add newline
80
			if (l < ll) {
81
				sb.push("\r\n");
82
			}
83
		}
84
		
85
		return sb.join("");
86
	},
87
	
88
    none:null,
89
}
90
91
// register funcs
92
UB.registerFuncs(Array.prototype, arrayFuncs);
93
94
95
96
var stringFuncs = {
97
98
	/**
99
	 * Loads the given CSV file, decoding the cell data and returning it as an array.
100
	 * Extremely robust and fast CSV parser. Only parser that works with all bizarre but valid test files.
101
	 * 
102
	 * 3 modes are available:
103
	 * if `headers` is null - All cells are returned as 2D array. (default)
104
	 * if `headers` is given and 0 length - The first row is stored in `headers`, remaining cells and returned as 2D array.
105
	 * if `headers` is given and >0 length - All rows are returned as objects, with the given headers treated as the prop names for the objects.
106
	 */
107
	loadCSV: function(encoding = "utf8", headers = null, trimValues = true, columnar = false, seperator = "auto") {
108
109
		// load text file
110
		var file = this.toString();
111
		var text = file.loadText(encoding);
112
		if (text == null) {
1 ignored issue
show
Best Practice introduced by
Comparing text to null using the == operator is not safe. Consider using === instead.
Loading history...
113
			return null;
114
		}
115
		
116
		// parse CSV string into Array
117
		return text.decodeCSV(headers, trimValues, columnar, seperator);
118
	},
119
120
	/** Decodes the given CSV file string and returns the cell data as an array.
121
	 *  Extremely robust and fast CSV parser. Only parser that works with all bizarre but valid test files.
122
	 * 
123
	 * 3 modes are available:
124
	 * if `headers` is null - All cells are returned as 2D array. (default)
125
	 * if `headers` is given and 0 length - The first row is stored in `headers`, remaining cells and returned as 2D array.
126
	 * if `headers` is given and >0 length - All rows are returned as objects, with the given headers treated as the prop names for the objects.
127
	 */
128
	decodeCSV: function (headers, trimValues, columnar = false, seperator = "auto") {
129
		var csvString = this.toString();
130
	
131
		// cut String into lines
132
		var lines = csvString.trim().splitLines();
133
		var sep = seperator == "auto" ? UB.CSV_detectSeperator(csvString, lines.length) : seperator;
134
		
135
		// config
136
		var hasHeaders = headers != null;
1 ignored issue
show
Best Practice introduced by
Comparing headers to null using the != operator is not safe. Consider using !== instead.
Loading history...
137
		var returnAsObjs = headers.exists();
138
139
		// status
140
		var inQuoted = false;
141
		
142
		// result
143
		var linesData = [];
144
		var word = [];
145
		var tempHeaders = [];
146
147
		// per line
148
		for (var l = 0, ll = lines.length; l < ll; l++) {
149
			var line = lines[l];
150
			var isHeader = (l == 0 && hasHeaders);
1 ignored issue
show
Best Practice introduced by
Comparing l to 0 using the == operator is not safe. Consider using === instead.
Loading history...
151
			
152
			// if we are in quoted text
153
			if (inQuoted) {
0 ignored issues
show
Comprehensibility Documentation Best Practice introduced by
This code block is empty. Consider removing it or adding a comment to explain.
Loading history...
154
				
155
				// keep taking chars
156
				
157
			}else{
158
				
159
				// save words into headers array / new array
160
				var lineWords = [];
161
				if (isHeader){
162
					if (returnAsObjs){
163
						lineWords = tempHeaders;
164
					}else{
165
						lineWords = headers;
166
					}
167
				}
168
				if (!isHeader) {
169
					linesData.push(lineWords);
170
				}
171
				
172
			}
173
			
174
			// per char
175
			for (var c = 0, clast = line.length - 1; c <= clast; c++) {
176
				var ch = line.charAt(c);
177
				
178
				// if we are in quoted text
179
				if (inQuoted) {
180
					
181
					// quotes..
182
					if (ch == "\"") {
183
						
184
						// quote may be escaped
185
						if (line.charAt(c + 1) == "\"") {
186
							c++;
1 ignored issue
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable c here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
187
							word.push("\"");
188
						}else {
189
							
190
							// quote means ending quoted text
191
							inQuoted = false;
192
						}
193
						
194
						continue;
195
					}
196
					
197
					// normal char
198
					word.push(ch);
199
					
200
					
201
				}else {
202
					
203
					// quote means beginning quoted text
204
					if (ch == "\""){
205
						inQuoted = true;
206
						continue;
207
					}
208
					
209
					// comma means end of word
210
					if (ch == sep) {
211
						lineWords.push(trimValues ? word.join("").trim() : word.join(""));
0 ignored issues
show
Bug introduced by
The variable lineWords seems to not be initialized for all possible execution paths.
Loading history...
212
						word = [];
213
						continue;
214
					}
215
					
216
					// normal char
217
					word.push(ch);
218
					
219
					// newline means end of word
220
					if (c == clast) {
221
						lineWords.push(trimValues ? word.join("").trim() : word.join(""));
222
						word = [];
223
					}
224
					
225
				}
226
				
227
			}
228
			
229
			// at end of line take word
230
			if (!inQuoted && word.Length > 0) {
231
				lineWords.push(trimValues ? word.join("").trim() : word.join(""));
232
				word = [];
233
			}
234
		}
235
236
		// convert array to objs
237
		if (returnAsObjs){
238
239
			// go thru all rows
240
			for (var l = 0, ll = linesData.length; l < ll; l++) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable ll already seems to be declared on line 148. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
Comprehensibility Naming Best Practice introduced by
The variable l already seems to be declared on line 148. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
241
				var line = linesData[l];
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable line already seems to be declared on line 149. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
242
				var obj = {};
243
244
				// convert all cells to obj props
245
				for (var h = 0, hl = headers.length; h < hl; h++) {
246
					var header = headers[h];
247
					obj[header] = line[h];
248
				}
249
				linesData[l] = obj;
250
			}
251
		}
252
		
253
		// convert 2D array to columnar
254
		if (columnar && !returnAsObjs) {
255
			linesData = linesData.transpose();
256
		}
257
258
		return linesData;
259
	},
260
261
    none:null,
262
}
263
264
// register funcs
265
UB.registerFuncs(String.prototype, stringFuncs);
266
267
268
269
// UTILS
270
UB.CSV_seperators = [',', ';', ':', '\t'];
271
272
UB.CSV_detectSeperator = function(csvString, rowCount) {
0 ignored issues
show
Unused Code introduced by
The parameter rowCount is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
273
	var sepCount = UB.newArray(0, UB.CSV_seperators.length);
274
275
	var character;
276
277
	var quoted = false;
278
	var firstChar = true;
279
	var foundAny = false;
280
281
	var c = 0;
282
	var cl = csvString.length - 50;  // skip last few chars
283
	while (c < cl) {
284
		character = csvString.charAt(c);
285
		c++;
286
287
		switch (character) {
288
			case '"':
289
				if (quoted) {
290
					if (csvString.charAt(c + 1) != '"') {  // Value is quoted and current character is " and next character is not ".
291
						quoted = false;
292
					} else {
293
						c++;  // Value is quoted and current and next characters are "" - read (skip) peeked qoute.
294
					}
295
				} else {
296
					if (firstChar) {  // Set value as quoted only if this quote is the first char in the value.
297
						quoted = true;
298
					}
299
				}
300
				break;
301
			case '\r':
302
			case '\n':
303
				if (!quoted) {
304
					firstChar = true;
305
					continue;
306
				}
307
				break;
308
			default:
309
				if (!quoted) {
310
					var index = UB.CSV_seperators.indexOf(character);
311
					if (index != -1) {
312
						sepCount[index]++;
313
						firstChar = true;
314
						foundAny = true;
315
						continue;
316
					}
317
				}
318
				break;
319
		}
320
321
		if (firstChar) {
322
			firstChar = false;
323
		}
324
	}
325
326
	return !foundAny ? ',' : UB.CSV_seperators[sepCount.indexOfMax()];
327
}
328
329
330
//endRemoveIf(nodejs)
331